/*
    translate.c
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

#include "../debug.h"
#include "../global.h"
#include "../cpu.h"
#include "../access.h"
#include "regs-map.h"

#define PREFIX_DATA   0x08

/* operand size */
enum {
	OT_BYTE = 0,
	OT_WORD,
	OT_LONG,
	OT_QUAD,
};

/* i386 arith/logic operations */
enum {
	OP_ADDL = 0,
	OP_ORL,
	OP_ADCL,
	OP_SBBL,
	OP_ANDL,
	OP_SUBL,
	OP_XORL,
	OP_CMPL,
};

/* i386 shift ops */
enum {
	OP_ROL,
	OP_ROR,
	OP_RCL,
	OP_RCR,
	OP_SHL,
	OP_SHR,
	OP_SHL1, /* undocumented */
	OP_SAR = 7,
};

enum {
	CC_OP_NONE = 0,
	CC_OP_EFLAGS,

	CC_OP_LOGICB = 4,
	CC_OP_LOGICW,
	CC_OP_LOGICL,
	CC_OP_LOGICN,

	CC_OP_ADDB,
	CC_OP_ADDW,
	CC_OP_ADDL,
	CC_OP_ADDN,

	CC_OP_CMPB,
	CC_OP_CMPW,
	CC_OP_CMPL,
	CC_OP_CMPN,

	CC_OP_SUBB,
	CC_OP_SUBW,
	CC_OP_SUBL,
	CC_OP_SUBN,

	CC_OP_INCB,
	CC_OP_INCW,
	CC_OP_INCL,
	CC_OP_INCN,

	CC_OP_DECB,
	CC_OP_DECW,
	CC_OP_DECL,
	CC_OP_DECN,

	CC_OP_SHLB,
	CC_OP_SHLW,
	CC_OP_SHLL,
	CC_OP_SHLN,

	CC_OP_SHRB,
	CC_OP_SHRW,
	CC_OP_SHRL,
	CC_OP_SHRN,

	CC_OP_MULB,
	CC_OP_MULW,
	CC_OP_MULL,
	CC_OP_MULN,

	CC_OP_ADCB,
	CC_OP_ADCW,
	CC_OP_ADCL,
	CC_OP_ADCN,

	CC_OP_SBBB,
	CC_OP_SBBW,
	CC_OP_SBBL,
	CC_OP_SBBN,
};

#define PREFIX_REPZ   0x01
#define PREFIX_REPNZ  0x02
#define PREFIX_LOCK   0x04
#define PREFIX_DATA   0x08
#define PREFIX_ADR    0x10

typedef struct DisasContext {
	int is_jmp;
	int override; /* -1 if no override */
	uint8_t *cs_base;
	int pe;     /* protected mode */
	uint8_t *pc;
	int prefix;
	int aflag/*, dflag*/;
	int code32; /* 32 bit code segment */
	int ss32;   /* 32 bit stack segment */

	int cc_op;  /* current CC operation */
	int opc_valid;
	int cpl;
} DisasContext;

static uint32_t *gen_code_ptr;
static uint32_t gen_ldr_str_buf[100];
static uint32_t *gen_ldr_str_ptr = gen_ldr_str_buf;
static uint32_t *gen_b_label_buf[20];
static uint8_t gen_b_label_count = 0;

static inline uint32_t insn_get(DisasContext *s, int ot)
{
	uint32_t ret;

	switch(ot) {
	case OT_BYTE:
		ret = ldub_raw(s->pc);
		s->pc++;
		break;
	case OT_WORD:
		ret = lduw_raw(s->pc);
		s->pc += 2;
		break;
	default:
	case OT_LONG:
		ret = ldul_raw(s->pc);
		s->pc += 4;
		break;
	}
	return ret;
}

#include "gen_op.h"

/* an interrupt is different from an exception because of the
   priviledge checks */
static void gen_interrupt(DisasContext *s, int intno, 
                          unsigned int cur_eip, unsigned int next_eip)
{
	gen_x86_flags(s);

	if (s->pe == 0) {
		load_x86reg(OT_WORD, R0, R_ESP);
//		arm_sub_reg_imm(R0, 2, 0);
		gen_add_reg_SREG(T0, R0, R_SS, T1);
		arm_add_reg(T0, T0, MI);
	
		load_x86env(OT_WORD, T1, eflags);
		arm_push_addr_reg(OT_WORD, T0, T1);
		arm_bic_reg_imm(T1, (TF_MASK | IF_MASK) >> 8, 12);
		store_x86env(OT_WORD, T1, eflags);
		load_x86sreg(T1, R_CS);
		arm_push_addr_reg(OT_WORD, T0, T1);
		gen_mov_reg_imm(T1, next_eip);
		arm_push_addr_reg(OT_WORD, T0, T1);
	
		arm_sub_reg_imm(R0, 6, 0);
		store_x86reg(OT_WORD, R0, R_ESP);
	
		gen_mov_reg_imm(T0, intno);
		arm_mov_reg_lsl_imm(T0, T0, 2);
		arm_add_reg(T0, T0, MI);
		arm_pop_addr_reg(OT_WORD, T0, SPC);
		arm_pop_addr_reg(OT_WORD, T0, T1);
		store_x86sreg(T1, R_CS);
	}
}

/*
	parse ModR/M,SIB(if required),displacement(if required),
	store it to TMP1.
*/
static void gen_lea_modrm(DisasContext *s, int modrm, int add_seg)
{
	int32_t disp = 0;
	int havesib;
	int base;
	int index;
	int scale;
	int mod, rm, code, override;

	override = s->override;
	if (override >= 0)
		add_seg = 1;
	mod = (modrm >> 6) & 3;
	rm = modrm & 7;

	/* 32-Bit address */
	if (s->aflag) {
		havesib = 0;
		base = rm;
		index = 0;
		scale = 0;

		if (base == 4) {
			havesib = 1;
			code = ldub_raw(s->pc++);
			scale = (code >> 6) & 3;
			index = (code >> 3) & 7;
			base = (code & 7);
		}

		switch (mod) {
		case 0:
			if ((base & 7) == 5) {
				base = -1;
				disp = (int32_t)ldul_raw(s->pc);
				s->pc += 4;
			} else {
				disp = 0;
			}
			break;
		case 1:
			disp = (int8_t)ldub_raw(s->pc++);
			break;
		case 2:
			disp = (int32_t)ldul_raw(s->pc);
			s->pc += 4;
			break;
		default:
			printf("%s(%d): parse instruction error\n", __func__, __LINE__);
			break;
		}

		if (base >= 0) {
			load_x86reg(OT_LONG, T0, base);
			if (disp > 0)
				gen_add_reg_imm(T0, disp, T1);
			else if (disp)
				gen_sub_reg_imm(T0, -disp, T1);
		} else {
			gen_mov_reg_imm(T0, disp);
		}
		/* index == 4 means no index */
		if (havesib && (index != 4)) {
			load_x86reg(OT_LONG, T1, index);
			if (scale)
				arm_add_reg_lsl_imm(T0, T0, T1, scale);
		}
		if (add_seg) {
			if (override < 0) {
				if (base == R_EBP || base == R_ESP)
					override = R_SS;
				else
					override = R_DS;
			}
			gen_add_reg_SREG(T0, T0, override, T1);
		}
	/* 16-Bit address */
	} else {
		switch (mod) {
		case 0:
			if (rm == 6) {
				disp = lduw_raw(s->pc);
				s->pc += 2;
				gen_mov_reg_imm(T0, disp);
				rm = 0; /* avoid SS override */
				goto no_rm;
			} else {
				disp = 0;
			}
			break;
		case 1:
			disp = (int8_t)ldub_raw(s->pc++);
			break;
		case 2:
			disp = lduw_raw(s->pc);
			s->pc += 2;
			break;
		default:
			printf("%s(%d): parse instruction error\n", __func__, __LINE__);
			break;
		}
		switch (rm) {
		case 0:
			load_x86reg(OT_WORD, T0, R_EBX);
			load_x86reg(OT_WORD, T1, R_ESI);
			arm_add_reg(T0, T0, T1);
			break;
		case 1:
			load_x86reg(OT_WORD, T0, R_EBX);
			load_x86reg(OT_WORD, T1, R_EDI);
			arm_add_reg(T0, T0, T1);
			break;
		case 2:
			load_x86reg(OT_WORD, T0, R_EBP);
			load_x86reg(OT_WORD, T1, R_ESI);
			arm_add_reg(T0, T0, T1);
			break;
		case 3:
			load_x86reg(OT_WORD, T0, R_EBP);
			load_x86reg(OT_WORD, T1, R_EDI);
			arm_add_reg(T0, T0, T1);
			break;
		case 4:
			load_x86reg(OT_WORD, T0, R_ESI);
			break;
		case 5:
			load_x86reg(OT_WORD, T0, R_EDI);
			break;
		case 6:
			load_x86reg(OT_WORD, T0, R_EBP);
			break;
		default:
		case 7:
			load_x86reg(OT_WORD, T0, R_EBX);
			break;
		}
		if (disp > 0)
			gen_add_reg_imm(T0, disp, T1);
		else if (disp)
			gen_sub_reg_imm(T0, -disp, T1);
		arm_bic_reg_imm(T0, 0xff, 8);
		arm_bic_reg_imm(T0, 0xff, 4);
no_rm:
		if (add_seg) {
			if (override < 0) {
				if (rm == 2 || rm == 3 || rm == 6)
					override = R_SS;
				else
					override = R_DS;
			}
			gen_add_reg_SREG(T0, T0, override, T1);
		}
	}
}

static uint32_t disas_insn(CPUX86State *cpu, DisasContext *s)
{
	uint16_t b, prefixes, aflag, dflag;
	uint32_t next_eip, tval;
	uint32_t modrm, reg, rm, mod, op;
	int ot, val;
	uint8_t *pc_start;

	pc_start = s->pc;
	prefixes = 0;
	aflag = s->code32;
	dflag = s->code32;
	s->override = -1;

next_byte:
	b = ldub_raw(s->pc);
	s->pc++;
	/* check prefixes */
	switch (b) {
	/* Group 1:Lock and repeat prefixes */
	case 0xf3:
		prefixes |= PREFIX_REPZ;
		goto next_byte;
	case 0xf2:
		prefixes |= PREFIX_REPNZ;
		goto next_byte;
	case 0xf0:
		prefixes |= PREFIX_LOCK;
		goto next_byte;
	/* Group 2:Segment override prefixes */
	case 0x2e:
		s->override = R_CS;
		goto next_byte;
	case 0x36:
		s->override = R_SS;
		goto next_byte;
	case 0x3e:
		s->override = R_DS;
		goto next_byte;
	case 0x26:
		s->override = R_ES;
		goto next_byte;
	case 0x64:
		s->override = R_FS;
		goto next_byte;
	case 0x65:
		s->override = R_GS;
		goto next_byte;
	/* Group 3:Operand-size override prefix */
	case 0x66:
		prefixes |= PREFIX_DATA;
		goto next_byte;
	/* Group 4:Address-size overgen_op_movl_T0_0ride prefix */
	case 0x67:
		goto next_byte;
	}

	if (prefixes & PREFIX_DATA)
		dflag ^= 1;

	s->prefix = prefixes;
	s->aflag = aflag;

	/* now check op code */
reswitch:
	switch (b) {
	case 0x0f:
		/**************************/
		/* extended op code */
		b = ldub_raw(s->pc++) | 0x100;
		goto reswitch;
	        /**************************/

	/* arith & logic */
	case 0x00 ... 0x05:
	case 0x08 ... 0x0d:
	case 0x10 ... 0x15:
	case 0x18 ... 0x1d:
	case 0x20 ... 0x25:
	case 0x28 ... 0x2d:
	case 0x30 ... 0x35:
	case 0x38 ... 0x3d:
	{
		int op, f, val;

		op = (b >> 3) & 7;
		f = (b >> 1) & 3;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;

		switch (f) {
		case 0: /* OP Ev, Gv */
			modrm = ldub_raw(s->pc++);
			reg = (modrm >> 3) & 7;
			mod = (modrm >> 6) & 3;
			rm = modrm & 7;
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, OP1);
				gen_op_OP1_REG(s, op, ot, reg);
			} else {
				gen_op_REG_REG(s, op, ot, reg, rm);
			}
			break;
		case 1: /* OP Gv, Ev */
			modrm = ldub_raw(s->pc++);
			mod = (modrm >> 6) & 3;
			reg = (modrm >> 3) & 7;
			rm = modrm & 7;
			if (mod != 3) {
				if ((op == OP_ADCL) || (op == OP_SBBL)) {
					gen_x86_flags(s);
				}
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, OP2);
				gen_op_REG_OP2(s, op, ot, reg);
			} else {
				gen_op_REG_REG(s, op, ot, rm, reg);
			}
			break;
		case 2: /* OP A, Iv */
			val = insn_get(s, ot);
			gen_op_REG_IMM(s, op, ot, R_EAX, val);
			break;
		}
		break;
	}

	 /* GRP1 */
	case 0x80: /* Eb, Ib */
	case 0x81: /* Ev, Iv */
	case 0x82: /* Eb, Ib */
	case 0x83: /* Ev, Ib */
	{
		int val;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;

		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		rm = modrm & 7;
		op = (modrm >> 3) & 7;

		if (mod != 3) {
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			arm_ldr_data(ot, T0, OP1);
		}

		switch(b) {
		default:
		case 0x80:
		case 0x81:
		case 0x82:
			val = insn_get(s, ot);
			break;
		case 0x83:
			val = (int8_t)insn_get(s, OT_BYTE);
			break;
		}
		if (mod != 3) {
			gen_op_OP1_IMM(s, op, ot, val);
		} else {
			gen_op_REG_IMM(s, op, ot, rm, val);
		}
		break;
	}

	/**************************/
	/* inc, dec, and other misc arith */
	case 0x40 ... 0x47: /* inc Gv */
		ot = dflag ? OT_LONG : OT_WORD;
		gen_inc_REG(s, ot, R_EAX + (b & 7));
		break;
	case 0x48 ... 0x4f: /* dec Gv */
		ot = dflag ? OT_LONG : OT_WORD;
		gen_dec_REG(s, ot, R_EAX + (b & 7));
		break;

	case 0xf6: /* GRP3 */
	case 0xf7:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;

		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		rm = modrm & 7;
		op = (modrm >> 3) & 7;
		switch(op) {
		case 0: /* test */
			val = insn_get(s, ot);
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, OP1);
			} else {
				load_x86reg(ot, OP1, rm);
			}
			gen_and_reg_imm(OP1, val, T1);
			s->cc_op = CC_OP_LOGICB + ot;
			s->opc_valid = 1;
			break;
		case 2: /* not */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T1);
				arm_mvn_reg(T1, T1);
				arm_str_data(ot, T0, T1);
			} else {
				load_x86reg(ot, T1, rm);
				arm_mvn_reg(T1, T1);
				store_x86reg(ot, T1, rm);
			}
			break;
		case 3: /* neg */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T1);
				arm_rsb_reg_imm(T1, 0, 0);
				arm_str_data(ot, T0, T1);
			} else {
				load_x86reg(ot, T1, rm);
				arm_rsb_reg_imm(T1, 0, 0);
				store_x86reg(ot, T1, rm);
			}
			break;
		case 4: /* mul */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T1);
			} else {
				load_x86reg(ot, T1, rm);
			}
			load_x86reg(ot, T0, R_EAX);
			switch(ot) {
			case OT_BYTE:
				arm_mul(OP1, T0, T1);
				store_x86reg(OT_WORD, OP1, R_EAX);
				break;
			case OT_WORD:
				arm_mul(OP1, T0, T1);
				store_x86reg(OT_WORD, OP1, R_EAX);
				arm_mov_reg_lsr_imm(OP1, OP1, 16);
				store_x86reg(OT_WORD, OP1, R_EDX);
				break;
			case OT_LONG:
				arm_umull(OP1, T0, T0, T1);
				store_x86reg(OT_LONG, T0, R_EAX);
				store_x86reg(OT_LONG, OP1, R_EDX);
			default:
				break;
			}
			s->cc_op = CC_OP_MULB + ot;
			s->opc_valid = 1;
			break;
		case 5: /* imul */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T1);
			} else {
				load_x86reg(ot, T1, rm);
			}
			load_x86reg(ot, T0, R_EAX);
			switch(ot) {
			case OT_BYTE:
				arm_mul(OP1, T0, T1);
				store_x86reg(OT_WORD, OP1, R_EAX);
				break;
			case OT_WORD:
				arm_mul(OP1, T0, T1);
				store_x86reg(OT_WORD, OP1, R_EAX);
				arm_mov_reg_lsr_imm(OP1, OP1, 16);
				store_x86reg(OT_WORD, OP1, R_EDX);
				break;
			case OT_LONG:
				arm_smull(OP1, T0, T0, T1);
				store_x86reg(OT_LONG, T0, R_EAX);
				store_x86reg(OT_LONG, OP1, R_EDX);
			default:
				break;
			}
			s->cc_op = CC_OP_MULB + ot;
			s->opc_valid = 1;
			break;
		case 6: /* div */
		{
			uint32_t func = (uint32_t)gen_div;

			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T2);
			} else {
				load_x86reg(ot, T2, rm);
			}
			arm_push_lr();
			gen_mov_reg_imm(T0, (uint32_t)cpu);
			gen_mov_reg_imm(T1, ot);
			arm_mov_reg(arm_lr, arm_pc);
			gen_mov_reg_imm(arm_pc, func);
			arm_pop_lr();
			s->cc_op = CC_OP_NONE;
			break;
		}
		case 7: /* idiv */
		{
			uint32_t func = (uint32_t)gen_idiv;

			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T2);
			} else {
				load_x86reg(ot, T2, rm);
			}
			arm_push_lr();
			gen_mov_reg_imm(T0, (uint32_t)cpu);
			gen_mov_reg_imm(T1, ot);
			arm_mov_reg(arm_lr, arm_pc);
			gen_mov_reg_imm(arm_pc, func);
			arm_pop_lr();
			s->cc_op = CC_OP_NONE;
			break;
		}
		default:
			goto illegal_op;
		}
		break;

	case 0xfe: /* GRP4 */
	case 0xff: /* GRP5 */
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;

		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		rm = modrm & 7;
		op = (modrm >> 3) & 7;
		if (op >= 2 && b == 0xfe) {
			goto illegal_op;
		}
		switch(op) {
		case 0: /* inc Ev */
			if (mod != 3) {
//				gen_str_CF(s);
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, OP1);
				arm_add_rd_rn_imm(T1, OP1, 1, 0);
				store_x86reg(ot, T1, reg);
				s->cc_op = CC_OP_INCB + ot;
				s->opc_valid = 1;
			} else {
				gen_inc_REG(s, ot, rm);
			}
			break;
		case 1: /* dec Ev */
			if (mod != 3) {
//				gen_str_CF(s);
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, OP1);
				arm_sub_rd_rn_imm(T1, OP1, 1, 0);
				store_x86reg(ot, T1, reg);
				s->cc_op = CC_OP_DECB + ot;
				s->opc_valid = 1;
			} else {
				gen_dec_REG(s, ot, rm);
			}
			break;
		case 2: /* call Ev */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
			} else
				load_x86reg(ot, T0, rm);
			next_eip = s->pc - s->cs_base;
			arm_ldr_data(ot, T0, SPC);
			gen_push_imm(s, ot, next_eip);
			s->is_jmp = 1;
			break;
		case 3: /* lcall Ev */
			if (s->pe) {
			} else {
				if (s->ss32) {
					load_x86reg(ot, R0, R_ESP);
					arm_add_reg(T2, R0, MI);
				} else {
					load_x86reg(OT_WORD, R0, R_ESP);
					gen_add_reg_SREG(T2, R0, R_SS, T1);
					arm_add_reg(T2, T2, MI);
				}
				load_x86sreg(T1, R_CS);
				arm_push_addr_reg(ot, T2, T1);
				arm_sub_reg_imm(R0, (ot == OT_WORD)? 4 : 8, 0);
				store_x86reg(ot, R0, R_ESP);

				if (mod != 3) {
					gen_lea_modrm(s, modrm, !s->pe);
					arm_add_reg(T0, T0, MI);
				} else
					load_x86reg(ot, T0, rm);
				arm_pop_addr_reg(ot, T0, SPC);
				arm_ldr_data(ot, T0, T1);
				store_x86sreg(T1, R_CS);

				gen_mov_reg_imm(T1, s->pc - s->cs_base);
				arm_push_addr_reg(ot, T2, T1);

				s->is_jmp = 1;
			}
			break;
		case 4: /* jmp Ev */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
			} else
				load_x86reg(ot, T0, rm);
			arm_ldr_data(ot, T0, SPC);
			s->is_jmp = 1;
			break;
		case 5: /* ljmp Ev */
			goto illegal_op;
		case 6: /* push Ev */
			if (mod != 3) {
				gen_lea_modrm(s, modrm, !s->pe);
				arm_add_reg(T0, T0, MI);
				arm_ldr_data(ot, T0, T1);
			} else
				load_x86reg(ot, T1, rm);
			if (s->ss32) {
				load_x86reg(ot, T0, R_ESP);
				arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
				store_x86reg(ot, T0, R_ESP);
			} else {
				load_x86reg(OT_WORD, T0, R_ESP);
				arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
				store_x86reg(OT_WORD, T0, R_ESP);
				gen_add_reg_SREG(T0, T0, R_SS, T2);
			}
			arm_add_reg(T0, T0, MI);
			arm_str_data(ot, T0, T1);
			break;
		default:
			goto illegal_op;
		}
		break;

	case 0x84: /* test Ev, Gv */
	case 0x85:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;

		modrm = ldub_raw(s->pc++);
		reg = (modrm >> 3) & 7;
		mod = (modrm >> 6) & 3;
		rm = (modrm & 7);
		if (mod == 3) {
			load_x86reg(ot, T0, reg);
			if (reg == rm) {
				arm_and_reg(OP1, T0, T0);
			} else {
				load_x86reg(ot, T1, rm);
				arm_and_reg(OP1, T1, T0);
			}
		} else {
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			arm_ldr_data(ot, T0, T1);
			load_x86reg(ot, T0, reg);
			arm_and_reg(OP1, T1, T0);
		}
		s->cc_op = CC_OP_LOGICB + ot;
		s->opc_valid = 1;
		break;

	case 0xa8: /* test eAX, Iv */
	case 0xa9:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		val = insn_get(s, ot);
		load_x86reg(ot, OP1, R_EAX);
		gen_and_reg_imm(OP1, val, T0);
		s->cc_op = CC_OP_LOGICB + ot;
		s->opc_valid = 1;
		break;

	case 0x98: /* CWDE/CBW */
		goto illegal_op;

	case 0x99: /* CDQ/CWD */
		if (dflag == 1) {
			load_x86reg(OT_LONG, T0, R_EAX);
			arm_mov_reg_asr_imm(T0, T0, 31);
			store_x86reg(OT_LONG, T0, R_EDX);
		} else {
			load_x86reg(OT_WORD, T0, R_EAX);
			arm_mov_reg_lsl_imm(T0, T0, 16);
			arm_mov_reg_asr_imm(T0, T0, 31);
			store_x86reg(OT_WORD, T0, R_EDX);
		}
		break;

	case 0x1af: /* imul Gv, Ev */
	case 0x69: /* imul Gv, Ev, I */
	case 0x6b:
		goto illegal_op;

	case 0x1c0:
	case 0x1c1: /* xadd Ev, Gv */
		goto illegal_op;

	case 0x1b0:
	case 0x1b1: /* cmpxchg Ev, Gv */
		goto illegal_op;

	case 0x1c7: /* cmpxchg8b */
		goto illegal_op;

	/**************************/
	/* push/pop */
	case 0x50 ... 0x57: /* push */
		ot = dflag + OT_WORD;
		if (s->ss32) {
			load_x86reg(ot, T0, R_ESP);
			arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
			store_x86reg(ot, T0, R_ESP);
		} else {
			load_x86reg(OT_WORD, T0, R_ESP);
			arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
			store_x86reg(OT_WORD, T0, R_ESP);
			gen_add_reg_SREG(T0, T0, R_SS, T1);
		}
		arm_add_reg(T0, T0, MI);
		load_x86reg(ot, T1, b & 7);
		arm_str_data(ot, T0, T1);
		break;
	case 0x58 ... 0x5f: /* pop */
		ot = dflag + OT_WORD;
		if (s->ss32) {
			load_x86reg(ot, R0, R_ESP);
			arm_add_reg(T0, R0, MI);
		} else {
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);
		}
		arm_ldr_data(ot, T0, T1);
		store_x86reg(ot, T1, b & 7);
		arm_add_reg_imm(R0, (ot == OT_WORD)? 2 : 4, 0);
		if (s->ss32) {
			store_x86reg(ot, R0, R_ESP);
		} else {
			store_x86reg(OT_WORD, R0, R_ESP);
		}
		break;
	case 0x60: /* pusha */
	{
		int tmp;
		ot = dflag + OT_WORD;
		tmp = (ot == OT_WORD)? 2 : 4;

		if (s->ss32) {
			load_x86reg(ot, R0, R_ESP);
			arm_add_reg(T0, R0, MI);
		} else {
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);
		}
//		arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
		load_x86reg(ot, T1, R_EAX);
		arm_push_addr_reg(ot, T0, T1);
		load_x86reg(ot, T1, R_ECX);
		arm_push_addr_reg(ot, T0, T1);
		load_x86reg(ot, T1, R_EDX);
		arm_push_addr_reg(ot, T0, T1);
		load_x86reg(ot, T1, R_EBX);
		arm_push_addr_reg(ot, T0, T1);
		arm_push_addr_reg(ot, T0, R0);
		load_x86reg(ot, T1, R_EBP);
		arm_push_addr_reg(ot, T0, T1);
		load_x86reg(ot, T1, R_ESI);
		arm_push_addr_reg(ot, T0, T1);
		load_x86reg(ot, T1, R_EDI);
		arm_push_addr_reg(ot, T0, T1);
		arm_sub_reg_imm(R0, (ot == OT_WORD)? 16 : 32, 0);
		if (s->ss32) {
			store_x86reg(ot, R0, R_ESP);
		} else {
			store_x86reg(OT_WORD, R0, R_ESP);
		}
		break;
	}
	case 0x61: /* popa */
		ot = dflag + OT_WORD;
		if (s->ss32) {
			load_x86reg(ot, R0, R_ESP);
			arm_add_reg(T0, R0, MI);
		} else {
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);
		}
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_EDI);
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_ESI);
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_EBP);
		arm_add_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_EBX);
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_EDX);
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_ECX);
		arm_pop_addr_reg(ot, T0, T1);
		store_x86reg(ot, T1, R_EAX);
		arm_add_reg_imm(R0, (ot == OT_WORD)? 16 : 32, 0);
		if (s->ss32) {
			store_x86reg(ot, R0, R_ESP);
		} else {
			store_x86reg(OT_WORD, R0, R_ESP);
		}
		break;
	case 0x68: /* push Iv */
	case 0x6a:
		ot = dflag + OT_WORD;
		if (b == 0x68)
			val = insn_get(s, ot);
		else
			val = (int8_t)insn_get(s, OT_BYTE);
		gen_push_imm(s, ot, val);
		break;
	case 0x8f: /* pop Ev */
		goto illegal_op;
	case 0xc8: /* enter */
		goto illegal_op;
	case 0xc9: /* leave */
		goto illegal_op;
	case 0x06: /* push es */
	case 0x0e: /* push cs */
	case 0x16: /* push ss */
	case 0x1e: /* push ds */
		ot = dflag + OT_WORD;
		if (s->ss32) {
			load_x86reg(ot, T0, R_ESP);
			arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
			store_x86reg(ot, T0, R_ESP);
		} else {
			load_x86reg(OT_WORD, T0, R_ESP);
			arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
			store_x86reg(OT_WORD, T0, R_ESP);
			gen_add_reg_SREG(T0, T0, R_SS, T2);
		}
		arm_add_reg(T0, T0, MI);
		load_x86sreg(T1, b >> 3);
		arm_str_data(ot, T0, T1);
		break;
	case 0x1a0: /* push fs */
	case 0x1a8: /* push gs */
		goto illegal_op;
	case 0x07: /* pop es */
	case 0x17: /* pop ss */
	case 0x1f: /* pop ds */
		ot = dflag + OT_WORD;
		if (s->ss32) {
			load_x86reg(ot, R0, R_ESP);
			arm_add_reg(T0, R0, MI);
		} else {
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);
		}
		arm_ldr_data(ot, T0, T1);
		store_x86sreg(T1, b >> 3);
		arm_add_reg_imm(R0, (ot == OT_WORD)? 2 : 4, 0);
		if (s->ss32) {
			store_x86reg(ot, R0, R_ESP);
		} else {
			store_x86reg(OT_WORD, R0, R_ESP);
		}
		if (reg == R_SS) {
			/* if reg == SS, inhibit interrupts/trace. */
			/* If several instructions disable interrupts, only the
			_first_ does it */
		}
		break;
	case 0x1a1: /* pop fs */
	case 0x1a9: /* pop gs */
		goto illegal_op;

	/**************************/
	/* mov */
	case 0x88:
	case 0x89: /* mov Gv, Ev */
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		modrm = ldub_raw(s->pc++);
		reg = (modrm >> 3) & 7;
		mod = (modrm >> 6) & 3;
		rm = (modrm & 7);
		if (mod == 3) {
			gen_mov_REG_REG(ot, rm, reg);
		} else {
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			load_x86reg(ot, T1, reg);
			arm_str_data(ot, T0, T1);
		}
		break;
	case 0xc6:
	case 0xc7: /* mov Ev, Iv */
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		if (mod != 3) {
			gen_lea_modrm(s, modrm, !s->pe);
		}
		val = insn_get(s, ot);
		gen_mov_reg_imm(T1, val);
		if (mod != 3) {
			arm_add_reg(T0, T0, MI);
			arm_str_data(ot, T0, T1);
		} else
			store_x86reg(ot, T0, modrm & 7);
		break;

	case 0x8a:
	case 0x8b: /* mov Ev, Gv */
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = OT_WORD + dflag;
		modrm = ldub_raw(s->pc++);
		reg = (modrm >> 3) & 7;
		mod = (modrm >> 6) & 3;
		rm = (modrm & 7);
		if (mod == 3) {
			gen_mov_REG_REG(ot, rm, reg);
		} else {
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			arm_ldr_data(ot, T0, T1);
			store_x86reg(ot, T1, reg);
		}
		break;
	case 0x8e: /* mov seg, Gv */
		/* The MOV instruction cannot be used to load the CS register. */
		modrm = ldub_raw(s->pc++);
		reg = (modrm >> 3) & 7;
		if (reg >= 6 || reg == R_CS)
			goto illegal_op;
		mod = (modrm >> 6) & 3;
		rm = (modrm & 7);
		if (mod == 3) {
			if (s->pe) {
#if 0
				uint32_t func = (uint32_t)load_seg_without_cs;
			
				gen_push_lr();
				gen_mov_reg_imm(T0, (uint32_t)cpu);
				gen_mov_reg_imm(T1, sreg);
				load_x86reg(OT_WORD, T2, reg);
				/* ldr */
				gen_mov_reg(arm_lr, arm_pc);
				gen_mov_reg_imm(arm_pc, func);
				gen_pop_pc();
#endif
			} else {
				load_x86reg(OT_WORD, T0, rm);
				store_x86sreg(T0, reg);
			}
		} else {
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			if (s->pe) {
			} else {
				arm_ldr_data(OT_WORD, T0, T1);
				store_x86sreg(T1, reg);
			}
		}
		if (reg == R_SS) {
		/* if reg == SS, inhibit interrupts/trace */
		/* If several instructions disable interrupts, only the
		_first_ does it */
		}
		break;
	case 0x8c: /* mov Gv, seg */
		modrm = ldub_raw(s->pc++);
		reg = (modrm >> 3) & 7;
		mod = (modrm >> 6) & 3;
		rm = modrm & 7;
		if (reg >= 6)
			goto illegal_op;
		if (mod == 3) {
			ot = OT_WORD + dflag;
			load_x86sreg(T0, reg);
			store_x86reg(ot, T0, rm);
		} else {
			ot = OT_WORD;
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			load_x86sreg(T1, reg);
			arm_str_data(ot, T0, T1);
		}
		break;
	case 0x1b6: /* movzbS Gv, Eb */
	case 0x1b7: /* movzwS Gv, Eb */
	case 0x1be: /* movsbS Gv, Eb */
	case 0x1bf: /* movswS Gv, Eb */
	{
		 /* can optimized by v7. e.g. sxtb */
		int d_ot;
		/* d_ot is the size of destination */
		d_ot = dflag + OT_WORD;
		/* ot is the size of source */
		ot = (b & 1) + OT_BYTE;
		modrm = ldub_raw(s->pc++);
		reg = (modrm >> 3) & 7;
		mod = (modrm >> 6) & 3;
		rm = modrm & 7;

		if (mod == 3) {
			load_x86reg(ot, T1, rm);
		} else {
			gen_lea_modrm(s, modrm, !s->pe);
			arm_add_reg(T0, T0, MI);
			arm_ldr_data(ot, T0, T1);
		}
		switch(ot | (b & 8)) {
		case OT_BYTE:
			extub(T1, T1);
			break;
		case OT_BYTE | 8:
			extsb(T1, T1);
			break;
		case OT_WORD:
			extuw(T1, T1);
			break;
		default:
		case OT_WORD | 8:
			extsw(T1, T1);
			break;
		}
		store_x86reg(d_ot, T1, reg);
	}
	break;

	case 0x8d: /* lea */
		ot = dflag + OT_WORD;
		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		if (mod == 3)
			goto illegal_op;
		reg = (modrm >> 3) & 7;
		/* we must ensure that no segment is added */
		s->override = -1;
		gen_lea_modrm(s, modrm, 0);
		store_x86reg(ot, T0, reg);
		break;

	case 0xa0: /* mov EAX, Ov */
	case 0xa1:
	case 0xa2: /* mov Ov, EAX */
	case 0xa3:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		if (s->aflag) {
			tval = insn_get(s, OT_LONG);
		} else {
			tval = insn_get(s, OT_WORD);
		}
		gen_mov_reg_imm(T0, tval);
		gen_add_reg_SREG(T0, T0, R_DS, T1);
		arm_add_reg(T0, T0, MI);
		if ((b & 2) == 0) {
			arm_ldr_data(ot, T0, T1);
			store_x86reg(ot, T1, R_EAX);
		} else {
			load_x86reg(ot, T1, R_EAX);
			arm_str_data(ot, T0, T1);
		}
		break;

	case 0xd7: /* xlat */
		goto illegal_op;
	case 0xb0 ... 0xb7: /* mov R, Ib */
		ot = OT_BYTE;
		val = insn_get(s, ot);
		reg = b & 7;
		gen_mov_reg_imm(T0, val);
		store_x86reg(ot, T0, reg);
		break;
	case 0xb8 ... 0xbf: /* mov R, Iv */
		ot = dflag ? OT_LONG : OT_WORD;
		val = insn_get(s, ot);
		reg = b & 7;
		gen_mov_reg_imm(T0, val);
		store_x86reg(ot, T0, reg);
		break;

	case 0x91 ... 0x97: /* xchg R, EAX */
		ot = dflag + OT_WORD;
		reg = b & 7;
		load_x86reg(ot, T0, reg);
		load_x86reg(ot, T1, R_EAX);
		store_x86reg(ot, T0, R_EAX);
		store_x86reg(ot, T1, reg);
		break;
	case 0x86:
	case 0x87: /* xchg Ev, Gv */
		goto illegal_op;

	case 0xc4: /* les Gv */
		goto illegal_op;
	case 0xc5: /* lds Gv */
		goto illegal_op;
	case 0x1b2: /* lss Gv */
		goto illegal_op;
	case 0x1b4: /* lfs Gv */
		goto illegal_op;
	case 0x1b5: /* lgs Gv */
		goto illegal_op;

	/************************/
	/* shifts */
	case 0xc0:
	case 0xc1: /* shift Ev,Ib */
	{
		uint32_t opreg;
		uint8_t shift;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		op = (modrm >> 3) & 7;

		if (mod != 3) {
			goto illegal_op;
		} else {
			opreg = modrm & 7;
		}
		shift = ldub_raw(s->pc++);
		gen_shifti(s, op, ot, opreg, shift);
		break;
	}
	case 0xd0:
	case 0xd1:
	{
		/* shift Ev,1 */
		uint32_t opreg;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		op = (modrm >> 3) & 7;
		if (mod != 3) {
			goto illegal_op;
		} else {
			opreg = modrm & 7;
		}
		gen_shifti(s, op, ot, opreg, 1);
		break;
	}
	case 0xd2:
	case 0xd3:
	{
		/* shift Ev,cl */
		uint32_t opreg;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		op = (modrm >> 3) & 7;
		if (mod != 3) {
			goto illegal_op;
		} else {
			opreg = modrm & 7;
		}
		load_x86reg(OT_BYTE, OP2, R_ECX);
		gen_shift(s, op, ot, opreg, OP2);
		break;
	}

	case 0x1a4: /* shld imm */
		goto illegal_op;
	case 0x1a5: /* shld cl */
		goto illegal_op;
	case 0x1ac: /* shrd imm */
		goto illegal_op;
	case 0x1ad: /* shrd cl */
		goto illegal_op;

	/************************/
	/* floats */
	case 0xd8 ... 0xdf:
		goto illegal_op;

	/************************/
	/* string ops */
	case 0xa4: /* movsS */
	case 0xa5:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		if (prefixes & (PREFIX_REPZ | PREFIX_REPNZ)) {
			next_eip = s->pc - s->cs_base;
			if (dflag == 0)
				next_eip &= 0xffff;
			gen_rep_movs(s, ot, next_eip);
		} else {
			goto illegal_op;
		}
		break;
	case 0xaa: /* stosS */
	case 0xab:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		if (prefixes & (PREFIX_REPZ | PREFIX_REPNZ)) {
			next_eip = s->pc - s->cs_base;
			if (dflag == 0)
				next_eip &= 0xffff;
			gen_rep_stos(s, ot, next_eip);
		} else {
//			gen_stos(s, ot);
			goto illegal_op;
		}
		break;
	case 0xac: /* lodsS */
	case 0xad:
		goto illegal_op;
	case 0xae: /* scasS */
	case 0xaf:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		if (prefixes & PREFIX_REPNZ) {
			gen_repnz_scas(s, ot, s->pc - s->cs_base);
		} else if (prefixes & PREFIX_REPZ) {
			goto illegal_op;
//			gen_repz_scas(s, ot, pc_start - s->cs_base, s->pc - s->cs_base, 0);
		} else {
			goto illegal_op;
//			gen_scas(s, ot);
//			s->cc_op = CC_OP_SUBB + ot;
		}
		s->cc_op = CC_OP_SUBB + ot;
		s->opc_valid = 1;
		break;
	case 0xa6: /* cmpsS */
	case 0xa7:
		goto illegal_op;
	case 0x6c: /* insS */
	case 0x6d:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		if (prefixes & (PREFIX_REPZ)) {
			next_eip = s->pc - s->cs_base;
			if (dflag == 0)
				next_eip &= 0xffff;
			gen_rep_ins(s, ot, next_eip);
		} else {
//			gen_ins(s, ot);
			goto illegal_op;
		}
		break;
	case 0x6e: /* outsS */
	case 0x6f:
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag + OT_WORD;
		if (prefixes & (PREFIX_REPZ)) {
			next_eip = s->pc - s->cs_base;
			if (dflag == 0)
				next_eip &= 0xffff;
			gen_rep_outs(s, ot, next_eip);
		} else {
//			gen_ous(s, ot);
			goto illegal_op;
		}
		s->is_jmp = 1;
		break;

	/************************/
	/* port I/O */
	case 0xe4: /* IN */
	case 0xe5:
	{
		uint32_t func = (uint32_t)ioport_read;
	
		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag ? OT_LONG : OT_WORD;
		val = ldub_raw(s->pc++);
		arm_push_lr();
		gen_mov_reg_imm(T0, ot);
		gen_mov_reg_imm(T1, val);
		arm_mov_reg(arm_lr, arm_pc);
		gen_mov_reg_imm(arm_pc, func);
		store_x86reg(ot, T0, R_EAX)
		arm_pop_lr();
		break;
	}
	case 0xe6: /* OUT */
	case 0xe7:
	{
		uint32_t func = (uint32_t)ioport_write;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag ? OT_LONG : OT_WORD;
		val = ldub_raw(s->pc++);
		arm_push_lr();
		gen_mov_reg_imm(T0, ot);
		gen_mov_reg_imm(T1, val);
		load_x86reg(ot, T2, R_EAX);
		arm_mov_reg(arm_lr, arm_pc);
		gen_mov_reg_imm(arm_pc, func);
		arm_pop_lr();
		break;
	}
	case 0xec:
	case 0xed:
	{
		uint32_t func = (uint32_t)ioport_read;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag ? OT_LONG : OT_WORD;
		arm_push_lr();
		gen_mov_reg_imm(T0, ot);
		load_x86reg(OT_WORD, T1, R_EDX);
		arm_mov_reg(arm_lr, arm_pc);
		gen_mov_reg_imm(arm_pc, func);
		store_x86reg(ot, T0, R_EAX);
		arm_pop_lr();
		break;
	}
	case 0xee:
	case 0xef:
	{
		uint32_t func = (uint32_t)ioport_write;

		if ((b & 1) == 0)
			ot = OT_BYTE;
		else
			ot = dflag ? OT_LONG : OT_WORD;
		arm_push_lr();
		gen_mov_reg_imm(T0, ot);
		load_x86reg(OT_WORD, T1, R_EDX);
		load_x86reg(ot, T2, R_EAX);
		arm_mov_reg(arm_lr, arm_pc);
		gen_mov_reg_imm(arm_pc, func);
		arm_pop_lr();
		break;
	}

	/************************/
	/* control */
	case 0xc2: /* ret im */
		goto illegal_op;
	case 0xc3: /* ret */
		ot = dflag + OT_WORD;
		if (s->ss32) {
			load_x86reg(ot, R0, R_ESP);
			arm_add_reg(T0, R0, MI);
		} else {
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);
		}
		arm_ldr_data(ot, T0, SPC);

		arm_add_reg_imm(R0, (ot == OT_WORD)? 2 : 4, 0);
		if (s->ss32) {
			store_x86reg(ot, R0, R_ESP);
		} else {
			store_x86reg(OT_WORD, R0, R_ESP);
		}
		s->is_jmp = 1;
		break;
	case 0xca: /* lret im */
		goto illegal_op;
	case 0xcb: /* lret */
		if (!s->pe) {
			/* real mode */
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);

			arm_pop_addr_reg(OT_WORD, T0, SPC);
			arm_pop_addr_reg(OT_WORD, T0, T1);
			store_x86sreg(T1, R_CS);
			arm_add_reg_imm(R0, 4, 0);

			store_x86reg(OT_WORD, R0, R_ESP);
		}
		s->is_jmp = 1;
		break;
	case 0xcf: /* iret */
		if (!s->pe) {
			/* real mode */
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);

			arm_pop_addr_reg(OT_WORD, T0, SPC);
			arm_pop_addr_reg(OT_WORD, T0, T1);
			store_x86sreg(T1, R_CS);
			arm_pop_addr_reg(OT_WORD, T0, T1);
			store_x86env(OT_WORD, T1, eflags);
			arm_add_reg_imm(R0, 6, 0);

			store_x86reg(OT_WORD, R0, R_ESP);
			s->cc_op = CC_OP_EFLAGS;
			s->opc_valid = 1;
		}
		s->is_jmp = 1;
		break;
	case 0xe8: /* call im */
		if (dflag)
			tval = (int32_t)insn_get(s, OT_LONG);
		else
			tval = (int16_t)insn_get(s, OT_WORD);
		next_eip = s->pc - s->cs_base;
		tval += next_eip;
		if (dflag == 0)
			tval &= 0xffff;
		gen_push_imm(s, dflag + OT_WORD, next_eip);
		gen_mov_reg_imm(SPC, tval);
		s->is_jmp = 1;
		break;
	case 0x9a: /* lcall im */
		goto illegal_op;

	case 0xe9: /* jmp */
		if (dflag)
			tval = (int32_t)insn_get(s, OT_LONG);
		else
			tval = (int16_t)insn_get(s, OT_WORD);

		tval += s->pc - s->cs_base;
		if (dflag == 0)
			tval &= 0xffff;
		gen_jmp(tval);
		s->is_jmp = 1;
		break;
	case 0xea: /* ljmp im */
	{
		uint32_t selector, offset;
	
		ot = dflag ? OT_LONG : OT_WORD;
		offset = insn_get(s, ot);
		selector = insn_get(s, OT_WORD);
		gen_ljmp(s, cpu, selector, offset);
		s->is_jmp = 1;
		break;
	}
	case 0xeb: /* jmp Jb */
		tval = (int8_t)insn_get(s, OT_BYTE);
		tval += s->pc - s->cs_base;
		if (dflag == 0)
			tval &= 0xffff;
		gen_jmp(tval);
		s->is_jmp = 1;
		break;
	case 0x70 ... 0x7f: /* jcc Jb */
		tval = (int8_t)insn_get(s, OT_BYTE);
		goto do_jcc;
	case 0x180 ... 0x18f: /* jcc Jv */
		if (dflag) {
			tval = (int32_t)insn_get(s, OT_LONG);
		} else {
			tval = (int16_t)insn_get(s, OT_WORD);
		}
do_jcc:
		next_eip = s->pc - s->cs_base;
		tval += next_eip;
		if (dflag == 0)
			tval &= 0xffff;
		gen_jcc(s, b & 0xf, tval, next_eip);
		s->is_jmp = 1;
		break;

	case 0x190 ... 0x19f: /* setcc Eb */
		goto illegal_op;
	case 0x140 ... 0x14f: /* cmov Gv, Ev */
		goto illegal_op;

	/************************/
	/* flags */
	case 0x9c: /* pushf */
		if (s->pe == 0) {
			gen_x86_flags(s);

			load_x86reg(OT_WORD, T0, R_ESP);
			arm_sub_reg_imm(T0, 2, 0);
			store_x86reg(OT_WORD, T0, R_ESP);
			gen_add_reg_SREG(T0, T0, R_SS, T2);
			arm_add_reg(T0, T0, MI);
			load_x86env(OT_WORD, T1, eflags);
			arm_str_data(OT_WORD, T0, T1);
		}
		break;
	case 0x9d: /* popf */
		if (s->pe == 0) {
			load_x86reg(OT_WORD, R0, R_ESP);
			gen_add_reg_SREG(T0, R0, R_SS, T1);
			arm_add_reg(T0, T0, MI);
			arm_ldr_data(OT_WORD, T0, T1);
			store_x86env(OT_WORD, T1, eflags);
			arm_add_reg_imm(R0, 2, 0);
			store_x86reg(OT_WORD, R0, R_ESP);
			s->cc_op = CC_OP_EFLAGS;
			s->opc_valid = 1;
		}
		break;
	case 0x9e: /* sahf */
		goto illegal_op;
	case 0x9f: /* lahf */
		goto illegal_op;
	case 0xf5: /* cmc */
		goto illegal_op;
	case 0xf8: /* clc */
		goto illegal_op;
	case 0xf9: /* stc */
		goto illegal_op;
	case 0xfc: /* cld */
		gen_cld();
		break;
	case 0xfd: /* std */
		gen_std();
		break;

	/************************/
	/* bit operations */
	case 0x1ba: /* bt/bts/btr/btc Gv, im */
		goto illegal_op;
	case 0x1a3: /* bt Gv, Ev */
		goto illegal_op;
	case 0x1ab: /* bts */
		goto illegal_op;
	case 0x1b3: /* btr */
		goto illegal_op;
	case 0x1bb: /* btc */
		goto illegal_op;
	case 0x1bc: /* bsf */
	case 0x1bd: /* bsr */
		goto illegal_op;

	/************************/
	/* bcd */
	case 0x27: /* daa */
		goto illegal_op;
	case 0x2f: /* das */
		goto illegal_op;
	case 0x37: /* aaa */
		goto illegal_op;
	case 0x3f: /* aas */
		goto illegal_op;
	case 0xd4: /* aam */
		goto illegal_op;
	case 0xd5: /* aad */
		goto illegal_op;

	/************************/
	/* misc */
	case 0x90: /* nop */
		goto illegal_op;
	case 0x9b: /* fwait */
		goto illegal_op;
	case 0xcc: /* int3 */
		goto illegal_op;
	case 0xcd: /* int N */
		val = ldub_raw(s->pc++);
		gen_interrupt(s, val, pc_start - s->cs_base, s->pc - s->cs_base);
		s->is_jmp = 1;
		break;
	case 0xce: /* into */
		goto illegal_op;
	case 0xfa: /* cli */
		break;
	case 0xfb: /* sti */
		break;
	case 0x62: /* bound */
		goto illegal_op;
	case 0x1c8 ... 0x1cf: /* bswap reg */
		goto illegal_op;
	case 0xe0: /* loopnz */
	case 0xe1: /* loopz */
	/* FALL THRU */
		goto illegal_op;
	case 0xe2: /* loop */
	{
		int a_ot = s->aflag ? OT_LONG : OT_WORD;

		tval = (int8_t)insn_get(s, OT_BYTE);
		next_eip = s->pc - s->cs_base;
		tval += next_eip;
		if (dflag == 0)
			tval &= 0xffff;
		load_x86reg(a_ot, T0, R_ECX);
		arm_sub_reg_imm(T0, 1, 0);
		store_x86reg(a_ot, T0, R_ECX);
		arm_cmp_reg_imm(T0, 0, 0);
		gen_mov_reg_imm_cond(COND_EQ, SPC, next_eip);
		gen_jmp_cond(COND_NE, tval);
		s->is_jmp = 1;
		break;
	}
	case 0xe3: /* jecxz */
	{
		int a_ot = s->aflag ? OT_LONG : OT_WORD;

		tval = (int8_t)insn_get(s, OT_BYTE);
		next_eip = s->pc - s->cs_base;
		tval += next_eip;
		if (dflag == 0)
			tval &= 0xffff;
		load_x86reg(a_ot, T0, R_ECX)
		arm_cmp_reg_imm(T0, 0, 0);
		gen_mov_reg_imm_cond(COND_EQ, SPC, tval);
		gen_mov_reg_imm_cond(COND_NE, SPC, next_eip);
		s->is_jmp = 1;
		break;
	}
	case 0x1a2: /* cpuid */
		goto illegal_op;
	case 0xf4: /* hlt */
		goto illegal_op;
	/* Group 6 */
	case 0x100:
#if 0
		modrm = ldub_code(s->pc++);
		mod = (modrm >> 6) & 3;
		op = (modrm >> 3) & 7;
		switch(op) {
		case 0: /* sldt */
			break;
		case 2: /* lldt */
			break;
		case 1: /* str */
			break;
		case 3: /* ltr */
			break;
		case 4: /* verr */
		case 5: /* verw */
			break;
		default:
			goto illegal_op;
		}
#endif
		goto illegal_op;
	/* Group 7 */
	case 0x101:
		modrm = ldub_raw(s->pc++);
		mod = (modrm >> 6) & 3;
		op = (modrm >> 3) & 7;
		switch(op) {
		case 0: /* sgdt */
		case 1: /* sidt */
			goto illegal_op;
		case 2: /* lgdt */
		case 3: /* lidt */
			if (mod == 3)
				goto illegal_op;
			if (s->cpl != 0) {
			printf("FixMe: raise a exception\n");
			} else {
				gen_lea_modrm(s, modrm, 1);
				gen_ldt(s, dflag, op);
			}
			break;
		case 4: /* smsw */
			goto illegal_op;
		case 6: /* lmsw */
			goto illegal_op;
		case 7: /* invlpg */
			goto illegal_op;
		default:
			goto illegal_op;
		}
		break;
	case 0x108: /* invd */
	case 0x109: /* wbinvd */
		goto illegal_op;
	case 0x63: /* arpl */
		goto illegal_op;
	case 0x102: /* lar */
	case 0x103: /* lsl */
		goto illegal_op;
	case 0x118:
		goto illegal_op;
	case 0x120: /* mov reg, crN */
	case 0x122: /* mov crN, reg */
		if (s->cpl != 0) {
			printf("FixMe: raise a exception\n");
		} else {
			modrm = ldub_raw(s->pc++);
			if ((modrm & 0xc0) != 0xc0)
				goto illegal_op;
			rm = modrm & 7;
			reg = (modrm >> 3) & 7;
			switch(reg) {
			case 0:
			case 2:
			case 3:
			case 4:
				if (b & 2) {
					gen_mov_CR_REG(reg, rm);
				} else {
					gen_mov_REG_CR(rm, reg);
				}
				break;
			default:
				goto illegal_op;
			}
		}
		break;
	case 0x121: /* mov reg, drN */
	case 0x123: /* mov drN, reg */
		goto illegal_op;
	case 0x106: /* clts */
		goto illegal_op;
	default:
		goto illegal_op;
	}

	return (uint32_t)(s->pc - pc_start);
illegal_op:
	printf("unknown instrution %02x at %p\n", b, s->pc);
	exit(0);
}

#include "../flush_icache.h"

DisasContext context;
uint32_t gen_code(CPUX86State *cpu, uint32_t source_pc, uint32_t *arm_code_buf)
{
	context.pc = (uint8_t *)source_pc;
	context.cs_base = (uint8_t *)(cpu->segs[R_CS].selector << 4);
	context.pe = cpu->cr[0] & 1;
	context.code32 = !!(cpu->segs[R_CS].flags & DESC_B_MASK);
	context.ss32 = !!(cpu->segs[R_SS].flags & DESC_B_MASK);
	context.cpl = (cpu->hflags >> HF_CPL_SHIFT) & 3;
	context.opc_valid = 0;

	gen_code_ptr = arm_code_buf;

	while (1) {
		uint32_t len;

		len = disas_insn(cpu, &context);
		if (context.is_jmp) {
			context.is_jmp = 0;
			break;
		}
	}
	if (context.opc_valid) {
//		context.opc_valid = 0;
		gen_mov_reg_imm(OPC, context.cc_op);
//		if (debug_stop)
//			gen_x86_flags();
	}
	gen_exit_tb();
	if (debug_stop) {
		printf("IN:\n");
		disas_i386(stdout, (void *)source_pc, (unsigned long)(context.pc - source_pc), !context.code32);
	}
	flush_icache_range((unsigned long)arm_code_buf, (unsigned long)gen_code_ptr);

	return gen_code_ptr - arm_code_buf;
}
